package com.sixnimmt.app.client.game.presenter;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import org.atmosphere.gwt.client.AtmosphereClient;
import org.atmosphere.gwt.client.AtmosphereGWTSerializer;
import org.atmosphere.gwt.client.AtmosphereListener;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.Widget;
import com.sixnimmt.app.client.Constants;
import com.sixnimmt.app.client.game.GameServiceAsync;
import com.sixnimmt.app.client.game.dto.PlayerScore;
import com.sixnimmt.app.client.game.presenter.serializer.GameNotificationSerializer;
import com.sixnimmt.app.client.game.view.dialog.ScoreDialog;
import com.sixnimmt.app.client.game.view.handler.IndexedClickHandler;
import com.sixnimmt.app.client.i18n.SixNimmtMessages;
import com.sixnimmt.app.client.presenter.Presenter;
import com.sixnimmt.app.shared.game.domain.Card;
import com.sixnimmt.app.shared.game.domain.Stack;
import com.sixnimmt.app.shared.game.exception.NameAlreadyUsedException;
import com.sixnimmt.app.shared.game.exception.ServerException;
import com.sixnimmt.app.shared.game.information.ServerInformation;
import com.sixnimmt.app.shared.game.notification.CardAddedNotification;
import com.sixnimmt.app.shared.game.notification.CardsTakenNotification;
import com.sixnimmt.app.shared.game.notification.GameNotification;
import com.sixnimmt.app.shared.game.notification.GameOverNotification;
import com.sixnimmt.app.shared.game.notification.HasChoosenCardNotification;
import com.sixnimmt.app.shared.game.notification.HaveToChooseCardNotification;
import com.sixnimmt.app.shared.game.notification.HaveToChooseStackNotification;
import com.sixnimmt.app.shared.game.notification.InitPlayerHandNotification;
import com.sixnimmt.app.shared.game.notification.PlayerAddedNotification;
import com.sixnimmt.app.shared.game.notification.PlayerHasQuitNotification;
import com.sixnimmt.app.shared.game.notification.RevealCardsNotification;
import com.sixnimmt.app.shared.game.notification.RoundOverNotification;
import com.sixnimmt.app.shared.game.notification.ServerRestartedNotification;
import com.sixnimmt.app.shared.game.notification.StackChangeNotification;

public class GamePresenter implements Presenter {

	private static final Logger log = Logger.getLogger(GamePresenter.class.getName());

	public interface Display {

		void addOpponentArea(String playerName);

		void initMyArea(String myName);

		HasText getPlayerScore(String playerName);

		HasText getPlayerTakenCards(String playerName);

		Panel getCurrentPlayerHand();

		Panel getCardLocation(int stack, int position);

		Panel getPlayerPlayedCardLocation(String playerName);

		HasClickHandlers[] getStackIdentifiers();

		HasClickHandlers getStartButton();

		HasClickHandlers getRestartServerButton();

		Widget asWidget();

		ScoreDialog getScoreDialog();

	}

	private final GameServiceAsync gameService;
	// private final HandlerManager eventBus;
	private final Display display;
	private final SixNimmtMessages messages;
	private String myName;

	private List<Card> playerHand;

	private final List<HandlerRegistration> cardsHandlerRegistrations;
	private final List<HandlerRegistration> stacksHandlerRegistrations;

	private AtmosphereClient atmosphereClient;
	private final GameNotificationListener gameNotificationListener;
	private final AtmosphereGWTSerializer serializer = GWT.create(GameNotificationSerializer.class);

	public GamePresenter(GameServiceAsync gameService, HandlerManager eventBus, Display view, SixNimmtMessages messages) {
		this.gameService = gameService;
		// this.eventBus = eventBus;
		this.display = view;
		this.messages = messages;
		this.cardsHandlerRegistrations = new ArrayList<HandlerRegistration>();
		this.stacksHandlerRegistrations = new ArrayList<HandlerRegistration>();

		this.gameNotificationListener = new GameNotificationListener();
	}

	public void bind() {
		display.getStartButton().addClickHandler(new ClickHandler() {

			@Override
			public void onClick(ClickEvent event) {
				gameService.startGame(myName, new AsyncCallback<Void>() {

					@Override
					public void onFailure(Throwable caught) {
						log.warning(caught.getMessage());
						Window.alert(caught.getMessage());
					}

					@Override
					public void onSuccess(Void result) {
						log.info("Game started !");
					}
				});

			}
		});

		display.getRestartServerButton().addClickHandler(new ClickHandler() {

			@Override
			public void onClick(ClickEvent event) {
				gameService.restartServer(myName, new AsyncCallback<Void>() {

					@Override
					public void onFailure(Throwable caught) {
						log.warning(caught.getMessage());
						Window.alert(caught.getMessage());
					}

					@Override
					public void onSuccess(Void result) {
						log.info("Server restarted !");

					}
				});

			}
		});
	}

	@Override
	public void go(HasWidgets container) {
		bind();
		container.clear();
		container.add(display.asWidget());

		joinServer();

	}

	private void joinServer() {
		gameService.retrieveServerInformation(new AsyncCallback<ServerInformation>() {

			@Override
			public void onFailure(Throwable caught) {
				log.warning(caught.getMessage());
				Window.alert(caught.getMessage());
			}

			@Override
			public void onSuccess(ServerInformation result) {
				if (result.equals(ServerInformation.GAME_ALREADY_STARTED)) {
					Window.alert(messages.gameAlreadyStarted());
				} else if (result.equals(ServerInformation.SERVER_FULL)) {
					Window.alert(messages.gameIsFull());
				} else if (result.equals(ServerInformation.OK)) {
					log.info("Server OK");

					joinGame();

				}
			}
		});
	}

	private void joinGame() {
		myName = "";
		while (myName.trim().isEmpty()) {
			myName = Window.prompt(messages.enterYourName(), "");
			if (myName == null) {
				// player has cancelled.
				return;
			}
		}

		gameService.joinGame(myName, new AsyncCallback<List<String>>() {

			@Override
			public void onSuccess(List<String> playersNames) {
				initEnvironnement(playersNames);

			}

			@Override
			public void onFailure(Throwable caught) {
				if (caught instanceof NameAlreadyUsedException) {
					Window.alert(messages.nameAlreadyUsed());
					joinServer();
				} else if (caught instanceof ServerException) {
					joinServer();
				} else {
					log.warning(caught.getMessage());
					Window.alert(caught.getMessage());
				}
			}
		});
	}

	private void initEnvironnement(List<String> playersNames) {
		atmosphereClient = new AtmosphereClient(getUrl(), serializer, gameNotificationListener);
		atmosphereClient.start();

		display.initMyArea(myName);
		((Widget) display.getStartButton()).removeStyleName("invisible");
		((Widget) display.getRestartServerButton()).removeStyleName("invisible");

		if (playersNames != null) {
			for (String opponentName : playersNames) {
				// players.add(opponentName);
				display.addOpponentArea(opponentName);
			}
		}

	}

	private String getUrl() {
		return GWT.getModuleBaseURL() + "push/game?playerName=" + myName;
	}

	private void handleNotifications(final List<GameNotification> notifications) {
		if (notifications.isEmpty()) {
			return;
		}

		GameNotification notification = notifications.remove(0);
		log.info("perfom :" + notification.getClass().getName());

		if (notification instanceof InitPlayerHandNotification) {

			InitPlayerHandNotification initPlayerHandNotification = (InitPlayerHandNotification) notification;
			display.getCurrentPlayerHand().clear();
			playerHand = initPlayerHandNotification.playerHand;
			for (Card card : initPlayerHandNotification.playerHand) {
				Image img = new Image("images/cards/" + card.getValue() + ".png");
				img.setHeight(Constants.CARD_HEIGHT + "px");
				img.setWidth(Constants.CARD_WIDTH + "px");
				img.setAltText(card.toString());
				img.setStyleName("card");
				display.getCurrentPlayerHand().add(img);
			}

		} else if (notification instanceof CardAddedNotification) {

			CardAddedNotification cardAddedNotification = (CardAddedNotification) notification;
			Panel playerPlayedCardLocation = display.getPlayerPlayedCardLocation(cardAddedNotification.playerName);
			playerPlayedCardLocation.clear();
			Panel panel = display.getCardLocation(cardAddedNotification.stackIndex, cardAddedNotification.position);
			panel.clear();
			Image img = new Image("images/cards/" + cardAddedNotification.card.getValue() + ".png");
			img.setHeight(Constants.CARD_HEIGHT + "px");
			img.setWidth(Constants.CARD_WIDTH + "px");
			img.setAltText(cardAddedNotification.card.toString());
			img.setStyleName("card");
			panel.add(img);

		} else if (notification instanceof StackChangeNotification) {

			StackChangeNotification stackChangeNotification = (StackChangeNotification) notification;
			for (int i = 0; i < Stack.MAX_CARD_NUMBER; i++) {
				display.getCardLocation(stackChangeNotification.stackIndex, i).clear();
			}
			int i = 0;
			for (Card card : stackChangeNotification.stackCards) {
				Image img = new Image("images/cards/" + card.getValue() + ".png");
				img.setHeight(Constants.CARD_HEIGHT + "px");
				img.setWidth(Constants.CARD_WIDTH + "px");
				img.setAltText(card.toString());
				img.setStyleName("card");

				display.getCardLocation(stackChangeNotification.stackIndex, i).add(img);
				i++;
			}

		} else if (notification instanceof PlayerAddedNotification) {

			PlayerAddedNotification playerAddedNotification = (PlayerAddedNotification) notification;
			// players.add(playerAddedNotification.playerName);
			display.addOpponentArea(playerAddedNotification.playerName);

		} else if (notification instanceof CardsTakenNotification) {

			CardsTakenNotification cardsTakenNotification = (CardsTakenNotification) notification;
			HasText label = display.getPlayerTakenCards(cardsTakenNotification.playerName);
			label.setText(cardsTakenNotification.totalNumberOfCards + "");

		} else if (notification instanceof HasChoosenCardNotification) {

			HasChoosenCardNotification hasChoosenCardNotification = (HasChoosenCardNotification) notification;
			Panel panel = display.getPlayerPlayedCardLocation(hasChoosenCardNotification.playerName);
			panel.clear();
			Image img = new Image("images/cards/backside.png");
			img.setAltText("back");
			img.setHeight(Constants.CARD_HEIGHT * 0.5 + "px");
			img.setStyleName("card");
			panel.add(img);

		} else if (notification instanceof RevealCardsNotification) {

			RevealCardsNotification revealCardsNotification = (RevealCardsNotification) notification;

			for (String playerName : revealCardsNotification.choosenCards.keySet()) {

				Panel panel = display.getPlayerPlayedCardLocation(playerName);
				panel.clear();

				Image img = new Image("images/cards/" + revealCardsNotification.choosenCards.get(playerName).getValue()
						+ ".png");
				img.setAltText(revealCardsNotification.choosenCards.get(playerName).toString());
				img.setHeight(Constants.CARD_HEIGHT * 0.5 + "px");
				img.setStyleName("card");
				panel.add(img);

			}

		} else if (notification instanceof RoundOverNotification) {

			log.info("Round over !");
			RoundOverNotification roundOverNotification = (RoundOverNotification) notification;
			for (String playerName : roundOverNotification.updatedScores.keySet()) {
				HasText labelScore = display.getPlayerScore(playerName);
				labelScore.setText(roundOverNotification.updatedScores.get(playerName).toString());
				HasText labelTakenCards = display.getPlayerTakenCards(playerName);
				labelTakenCards.setText("0");
			}

			if (notification instanceof GameOverNotification) {
				log.info("Game over !");
				if (atmosphereClient != null) {
					atmosphereClient.stop();
				}
				((Widget) display.getRestartServerButton()).addStyleName("invisible");
				((Widget) display.getStartButton()).addStyleName("invisible");
				// Window.alert("Game over !\nReload your page to start a new game...");
				displayFinalScores(roundOverNotification.updatedScores);
				return;
			}

		} else if (notification instanceof HaveToChooseCardNotification) {

			int index = 0;
			for (Iterator<Widget> iterator = display.getCurrentPlayerHand().iterator(); iterator.hasNext();) {
				HasClickHandlers cardWidget = (HasClickHandlers) iterator.next();

				HandlerRegistration chr = cardWidget.addClickHandler(new IndexedClickHandler(index++) {

					@Override
					public void onClick(final ClickEvent event) {
						final Card playedCard = playerHand.get(getIndex());
						log.info(myName + " choose " + playedCard.getValue());
						final Widget widgetSource = (Widget) event.getSource();
						gameService.playCard(myName, playedCard, new AsyncCallback<Void>() {

							@Override
							public void onSuccess(Void result) {
								removeHandCardsClickHandler();
								playerHand.remove(playedCard);

								display.getCurrentPlayerHand().remove(widgetSource);

								Panel panel = display.getPlayerPlayedCardLocation(myName);
								panel.clear();

								Image img = new Image("images/cards/" + playedCard.getValue() + ".png");
								img.setAltText(playedCard.toString());
								img.setHeight(Constants.CARD_HEIGHT * 0.5 + "px");
								img.setStyleName("card");

								panel.add(img);
							}

							@Override
							public void onFailure(Throwable caught) {
								log.warning(caught.getMessage());
							}
						});

					}
				});
				((Widget) cardWidget).addStyleName("cursor-hand");
				cardsHandlerRegistrations.add(chr);

			}

		} else if (notification instanceof HaveToChooseStackNotification) {
			HasClickHandlers[] stackIdentifiers = display.getStackIdentifiers();
			for (int i = 0; i < stackIdentifiers.length; i++) {
				HandlerRegistration shr = stackIdentifiers[i].addClickHandler(new IndexedClickHandler(i) {

					@Override
					public void onClick(ClickEvent event) {
						final int stackIndex = getIndex();

						gameService.chooseStack(myName, stackIndex, new AsyncCallback<Void>() {

							@Override
							public void onSuccess(Void result) {

								removeStackClickHandler();

							}

							@Override
							public void onFailure(Throwable caught) {
								log.warning(caught.getMessage());
							}
						});

					}
				});
				stacksHandlerRegistrations.add(shr);
				((Widget) stackIdentifiers[i]).removeStyleName("invisible");
			}

		} else if (notification instanceof ServerRestartedNotification) {
			if (atmosphereClient != null) {
				atmosphereClient.stop();
			}
			Window.Location.reload();
		} else if (notification instanceof PlayerHasQuitNotification) {
			PlayerHasQuitNotification playerHasQuitNotification = (PlayerHasQuitNotification) notification;
			log.info("Player " + playerHasQuitNotification.playerName + " has quit !");
		}

		handleNotifications(notifications);

	}

	private void displayFinalScores(Map<String, Integer> updatedScores) {
		List<PlayerScore> finalScores = orderScore(updatedScores);

		display.getScoreDialog().getMainPanel().clear();
		FlexTable scoresTable = new FlexTable();

		int i = 0;
		for (PlayerScore playerScore : finalScores) {
			scoresTable.setText(i, 0, playerScore.getRank() + ".");
			scoresTable.setText(i, 1, playerScore.getPlayerName());
			scoresTable.setText(i, 2, playerScore.getScore() + "");
			i++;
		}

		display.getScoreDialog().getMainPanel().add(scoresTable);

		display.getScoreDialog().center();
		display.getScoreDialog().show();

	}

	public static List<PlayerScore> orderScore(Map<String, Integer> updatedScores) {
		List<PlayerScore> result = new ArrayList<PlayerScore>();

		for (String playerName : updatedScores.keySet()) {
			PlayerScore playerScore = new PlayerScore(playerName, updatedScores.get(playerName));
			result.add(playerScore);
		}

		Collections.sort(result);

		for (int i = 0; i < result.size(); i++) {
			if (i == 0) {
				result.get(i).setRank(i + 1);
			} else {
				if (result.get(i).getScore() == result.get(i - 1).getScore()) {
					result.get(i).setRank(result.get(i - 1).getRank());
				} else {
					result.get(i).setRank(i + 1);
				}
			}
		}

		return result;

	}

	private void removeHandCardsClickHandler() {
		for (HandlerRegistration handlerRegistration : cardsHandlerRegistrations) {
			handlerRegistration.removeHandler();
		}
		cardsHandlerRegistrations.clear();
		for (Iterator<Widget> iterator = display.getCurrentPlayerHand().iterator(); iterator.hasNext();) {
			Widget widget = iterator.next();
			widget.removeStyleName("cursor-hand");
		}
	}

	private void removeStackClickHandler() {
		for (HandlerRegistration handlerRegistration : stacksHandlerRegistrations) {
			handlerRegistration.removeHandler();
		}
		stacksHandlerRegistrations.clear();
		HasClickHandlers[] stackIdentifiers = display.getStackIdentifiers();
		for (int i = 0; i < stackIdentifiers.length; i++) {
			((Widget) stackIdentifiers[i]).addStyleName("invisible");
		}
	}

	private class GameNotificationListener implements AtmosphereListener {

		@Override
		public void onConnected(int heartbeat, int connectionID) {
		}

		@Override
		public void onBeforeDisconnected() {
		}

		@Override
		public void onDisconnected() {

		}

		@Override
		public void onError(Throwable exception, boolean connected) {
			log.severe("onError " + exception.toString());
		}

		@Override
		public void onHeartbeat() {
		}

		@Override
		public void onRefresh() {
		}

		@Override
		public void onAfterRefresh() {
		}

		@SuppressWarnings("unchecked")
		@Override
		public void onMessage(List<?> messages) {
			List<GameNotification> gameNotifications = new ArrayList<GameNotification>();
			gameNotifications.addAll((List<GameNotification>) messages);
			handleNotifications(gameNotifications);
		}
	}
}
